from django.shortcuts import render_to_response, redirect
from django.template.loader import get_template
from django.template import Context, Template, RequestContext
from django.http import HttpResponse
from django.utils import simplejson
from django.utils.decorators import decorator_from_middleware
from django.core import mail as djangomail
from astoundlearning_app.helper import Helper
from astoundlearning_app.bitly import BitLy
from astoundlearning_app.models import Preferences, CustomUser, StudySet, Contributor, SetEntry, SetSubscription, SetCurrentSubscription, StudyResult, Tag, FeedbackForAstoundLearning, Location, LocationDeck
from astoundlearning_app import jsmin
from google.appengine.api import memcache, users, mail
from google.appengine.ext import db
import pickle, os, urllib, datetime, traceback, logging, sys, settings, hashlib, csv, math
import opensocial
from html2text import html2text
from txt2html import plaintext2html

def sort_setlist(a,b):
    a_title = a[1].lower()
    b_title = b[1].lower()
    if (a_title < b_title):
        return -1
    elif (a_title == b_title):
        return 0
    else:
        return 1

def sort_location_decks(a,b):
    a_title = a[1].lower()
    b_title = b[1].lower();
    if (a_title < b_title):
        return -1
    elif (a_title == b_title):
        return 0
    else:
        return 1

def sort_locations(a,b):
    a_title = a[1].lower()
    b_title = b[1].lower()
    if (a_title < b_title):
        return -1
    elif (a_title == b_title):
        return 0
    else:
        return 1

def sort_decks_personal_library(a,b):
    a_title = a['title'].lower()
    b_title = b['title'].lower()
    if (a_title < b_title):
        return -1
    elif (a_title == b_title):
        return 0
    else:
        return 1

def plugin_sort_entries_whenAdded(a,b):
    a_whenAdded = a.whenAdded
    b_whenAdded = b.whenAdded
    if (a_whenAdded < b_whenAdded):
        return -1
    elif (a_whenAdded == b_whenAdded):
        return 0
    else:
        return 1

def plugin_sort_entries_history(a,b):
    if (a['shown'] < b['shown']):
        return -1
    elif (a['shown'] > b['shown']):
        return 1
    else:
        if (a['ratio'] > b['ratio']):
            return -1
        elif (a['ratio'] == b['ratio']):
            return 0
        else:
            return 1

def custom_context_processor(request):
    return {'siteName': 'AstoundLearning'}

def just_display_the_template_html(request, user, context, helper):
    pass

def robots_dot_txt(request):
    base = os.path.dirname(__file__)
    content = file(os.path.join(base,'templates/astoundlearning_app/robots.txt')).read()
    response = HttpResponse(content, mimetype='text/plain')
    return response

def json_wrapper(view):
    def new_view(request, *args, **kwargs):
        try:
            h = Helper(request)
            h.add_facebook_instance_to_the_request_object()
            context = h.prepareContext()
            kwargs['context'] = context
            kwargs['helper'] = h
            kwargs['user'] = context['user']
            json = view(request, *args, **kwargs)
            json_data = simplejson.dumps(json)
            response = HttpResponse(json_data, mimetype='application/json')
            return response
        except:
            lines = ''.join(traceback.format_exception(*sys.exc_info()))
            json = {'astoundlearning_error': True}
            json_data = simplejson.dumps(json)
            response = HttpResponse(json_data, mimetype='application/json')
            return response
    return new_view

def txt_wrapper(view):
    def new_view(request, *args, **kwargs):
        h = Helper(request)
        context = h.prepareContext()
        kwargs['context'] = context
        kwargs['helper'] = h
        kwargs['user'] = context['user']
        template_name = kwargs['template']
        del kwargs['template']
        txt = view(request, *args, **kwargs)
        response = HttpResponse(txt, mimetype='text/plain')
        return response
    return new_view

def html_wrapper(view):
    def new_view(request, *args, **kwargs):
        h = Helper(request)
        h.add_facebook_instance_to_the_request_object()
        context = h.prepareContext()
        if (h.get_current_user()):
            context['cached_deck_list'] = simplejson.dumps(setlist_json(request, h.get_current_user(), context, h))
        else:
            context['cached_deck_list'] = '{}'
            template_name = kwargs['template']
            if (template_name == 'astoundlearning_app/preferences.html'):
                return redirect(users.create_login_url(request.build_absolute_uri()))
        kwargs['context'] = context
        kwargs['helper'] = h
        kwargs['user'] = context['user']
        template_name = kwargs['template']
        del kwargs['template']
        view(request, *args, **kwargs)
        if (template_name.startswith('ajax')):
            template_name = 'astoundlearning_app/' + template_name 
        elif (template_name.startswith('facebook')):
            template_name = 'astoundlearning_app/' + template_name
        if (template_name == 'astoundlearning_app/deck_create_save.html'):
            return redirect(context['baseURL']);
        elif (template_name == 'astoundlearning_app/redirected.html'):
            return redirect(context['baseURL']);
        else:
            return render_to_response(template_name,context, context_instance = RequestContext(request,processors=[custom_context_processor]))
    return new_view

def display_page_html(request, *args, **kwargs):
    h = Helper(request)
    h.add_facebook_instance_to_the_request_object()
    context = h.prepareContext()
    if (h.get_current_user()):
        context['cached_deck_list'] = simplejson.dumps(setlist_json(request, h.get_current_user(), context, h))
    else:
        context['cached_deck_list'] = '{}'
    template_name = kwargs['template']
    return render_to_response(template_name, context)

def xml_wrapper(view):
    def new_view(request, *args, **kwargs):
        h = Helper(request)
        h.add_facebook_instance_to_the_request_object()
        context = h.prepareContext()
        kwargs['context'] = context
        kwargs['helper'] = h
        kwargs['user'] = context['user']
        del kwargs['template']
        xml = view(request, *args, **kwargs)
        response = HttpResponse(xml, mimetype='application/xml')
        return response
    return new_view

def maintenance(request):
    memcache.flush_all();
    response = HttpResponse('Maintenance is done.', mimetype='text/plain')
    return response

def serve_social_javascript(request):
    helper = Helper(request)
    context = helper.prepareContext()
    base = os.path.dirname(__file__)
    js = file(os.path.join(base,'javascript/jquery-1.2.6.min.js')).read() + "\n"
    js = js + file(os.path.join(base,'javascript/jquery.ui-1.5.2/ui/minified/jquery.ui.all.min.js')).read() + "\n"
    js = js + file(os.path.join(base,'javascript/plugins4jquery/blockUI.js')).read() + "\n"
    js = js + file(os.path.join(base,'javascript/jquery.scrollIntoView.js')).read() + "\n"
    js = js + file(os.path.join(base,'javascript/our.js')).read() + "\n"
    js = js + 'study.templates = ' + helper.combineTemplates(context) + ';' + "\n"
    js = js + file(os.path.join(base,'javascript/social.js')).read() + "\n"
    js = js + file(os.path.join(base,'javascript/jqModal.js')).read() + "\n\n"
    js = js + file(os.path.join(base,'javascript/jqDnR.js')).read() + "\n\n"
    js = js.decode('utf-8').encode('ascii','ignore')
    template = Template(js)
    js = template.render(Context(context))
    #js = jsmin.jsmin(js)
    return HttpResponse(js, mimetype='text/javascript')

def serve_combined_javascript_without_social(request):
    helper = Helper(request)
    context = helper.prepareContext()
    return HttpResponse(context['jsWithoutSocial'], mimetype='text/javascript')

def serve_combined_css_without_social(request):
    helper = Helper(request)
    context = helper.prepareContext()
    return HttpResponse(context['combinedCSS'], mimetype='text/css')

def send_tooltips_speechbubble_txt(request, user, context, helper):
    base = os.path.dirname(__file__)
    template = get_template('astoundlearning_app/tooltips_speechbubble.txt')
    return template.render(Context(context))

def display_social_xml(request, user, context, helper):
    base = os.path.dirname(__file__)
    css = file(os.path.join(base,'css/themes/flora/flora.tabs.css')).read();
    css = css + "\n" + file(os.path.join(base,'css/themes/flora/flora.css')).read();
    css = css + "\n" + file(os.path.join(base,'css/jqModal.css')).read()
    context['css'] = css
    template = get_template('astoundlearning_app/social.xml')
    return template.render(Context(context))

def set_export_csv(request):
    helper = Helper(request)
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    # We are sending the file using text/plain and .txt extension, but the file is a CSV file (using Python CSV module)
    filename = set.title
    filename = filename.replace(' ','');
    filename = filename +   '.csv'
    response = HttpResponse()
    response['Content-Type'] = 'text/csv'
    response['Pragma'] = 'public' # for IE
    response['Content-Disposition'] = 'attachment;filename=' + filename
    keys = request.REQUEST.getlist('entryKey')
    entries = []
    if (len(keys)):
        for entryKey in keys:
            entries.append(helper.getCache(entryKey))
    else:
        entries_t = SetEntry.all().filter('set =', set).fetch(100)
        while entries_t:
            for entry in entries_t:
                entries.append(entry)
            entries_t = SetEntry.all().filter('set =', set).filter('__key__ >', entries[-1].key()).fetch(100)

    writer = csv.writer(response, quoting=csv.QUOTE_ALL)
    for entry in entries:
        # do something
        # question = html2text(entry.question).strip()
        # answer = html2text(entry.answer).strip()
        question = entry.question
        answer = entry.answer
        row = [question, answer]
        row.extend(entry.tags)
        writer.writerow(row)
    return response

def set_import_json(request):
    # how to process the file
    helper = Helper(request)
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    file = request.FILES['fileToImport']
    csv_reader = csv.reader(file, quoting=csv.QUOTE_ALL)
    for row in csv_reader:
        # question = plaintext2html(row[0])
        # answer = plaintext2html(row[1])
        question = row[0]
        answer = row[1]
        fieldCount = len(row)
        tags = []
        if (len(row) > 2):
            tags = row[2:]
        entry = SetEntry(set = set, question = question, answer = answer, tags = tags)
        entry.put()
        set.entryCount = set.entryCount + 1
    set.deleteCache(all = True)
    set.put()
    json = {'status': 'success'}
    json_data = simplejson.dumps(json)
    response = HttpResponse(json_data, mimetype='text/html')
    return response

def set_add_json(request,user,context,helper):
    title = request.REQUEST.get('title')
    materials = request.REQUEST.get('materials')
    visibility = request.REQUEST.get('visibility')
    description = request.REQUEST.get('description')
    set = StudySet.gql("WHERE author = :author and title = :title", author = user, title = title).get()
    if (not set ):
        set = StudySet(author = user, title = title, visibility = visibility, studyMaterials = materials)
        set.description = description
        set.entryCount = 0
        set.subscriptionCount = 1
        set.put()
        set.subscribe(user)
        set.makeThisSetCurrentForUser(user)
        email = request.REQUEST.get('email')
        if (email):
            user.email = email;
            helper.deleteCache(str(user.key()))
            user.put()
    return setlist_json(request, user, context, helper)

def deck_create_save_html(request, user, context, helper):
    title = request.REQUEST.get('title')
    materials = request.REQUEST.get('materials')
    visibility = request.REQUEST.get('visibility')
    description = request.REQUEST.get('description')
    set = StudySet.gql("WHERE author = :author and title = :title", author = user, title = title).get()
    if (not set ):
        set = StudySet(author = user, title = title, visibility = visibility, studyMaterials = materials)
        set.description = description
        set.entryCount = 0
        set.subscriptionCount = 0
        set.put()
        set.subscribe(user)
        set.makeThisSetCurrentForUser(user)
        email = request.REQUEST.get('email')
        if (email):
            user.email = email;
            helper.deleteCache(str(user.key()))
            user.put()

def tag_add_json(request,user,context,helper):
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    tag = request.REQUEST.get('tag')
    return_data = {}
    if (tag is None) or (tag == '') or (tag == '+') or (tag == '-'):
        return_data['tags'] = set.tags()
        return return_data
    tags_dict = set.tags_dict();
    return_data['status'] = 'exist'
    if (tag not in tags_dict):
        n = Tag(set = set, tag = tag)
        n.put()
        return_data['status'] = 'added'
    set.deleteCache(all = True)
    tags = set.tags()
    return_data['tags'] = tags;
    return return_data

def tags_delete_json(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    tags = request.REQUEST.getlist('tags[]')
    for tag in tags:
        tag_obj = Tag.gql("WHERE set = :set AND tag = :tag", set = set, tag = tag).get()
        # remove the tag from entries
        if (tag_obj):
            entries = SetEntry.gql("WHERE set = :set AND tags = :tag", set = set, tag = tag).fetch(1000)
            for entry in entries:
                entry_tags = entry.tags
                entry_tags.remove(tag)
                entry.tags = tags
                helper.deleteCache(str(entry.key()))
                db.put(entries)
                # call set.deleteEntriesFromCache(entries)
            # delete the tag from the Tag table
            tag_obj.delete()
    set.deleteCache(all = True)
    tags = set.tags()
    return_data = {}
    return_data['tags'] = tags;
    return return_data

def tag_delete_json(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    tag_str = request.REQUEST.get('tag');

    tag_obj = Tag.gql("WHERE set = :set AND tag = :tag", set = set, tag = tag_str).get()

    # remove the tag from entries
    if (tag_obj):
        entries = SetEntry.gql("WHERE set = :set AND tags = :tag", set = set, tag = tag_str).fetch(1000)
        for entry in entries:
            tags = entry.tags
            tags.remove(tag_str)
            entry.tags = tags
            helper.deleteCache(str(entry.key()))
        db.put(entries)
        # call set.deleteEntriesFromCache(entries)

        # delete the tag from the Tag table
        tag_obj.delete()

        set.deleteCache(all = True)
    tags = set.tags()
    return_data = {}
    return_data['tags'] = tags;
    return return_data

def entry_add_tag_json(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    entryKey = request.REQUEST.get('entryKey')
    tag = request.REQUEST.get('tag')
    entry = helper.getCache(entryKey)
    if tag not in entry.tags:
        entry.tags.append(tag)
        entry.put()
    helper.deleteCache(entryKey)

def entry_delete_tag_json(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    entryKey = request.REQUEST.get('entryKey')
    tag = request.REQUEST.get('tag')
    entry = helper.getCache(entryKey)
    entry.tags.remove(tag)
    entry.put()
    helper.deleteCache(entryKey)

def entry_process_show_answer_json(request, user, context, helper):
    entryKey = request.REQUEST.get('entryKey')
    setKey = request.REQUEST.get('setKey')
    entry = helper.getCache(entryKey)
    result = StudyResult.gql("WHERE user = :user AND entry = :entry", user = user, entry = entry).get();
    if (not result):
        set = helper.getCache(setKey)
        entry = helper.getCache(entryKey)
        result = StudyResult(user = user, set = set, entry = entry)
    result.shown = int(request.REQUEST.get('shown'))
    result.wrong = int(request.REQUEST.get('wrong'))
    result.ratio = float(result.wrong / result.shown)
    result.put();
    return {}

def entry_add_json(request, user, context, helper):
    action = request.REQUEST.get('action')
    setKey = request.REQUEST.get('setKey')
    question = request.REQUEST.get('question')
    answer = request.REQUEST.get('answer')                         
    tags = request.REQUEST.getlist('tags[]')
    entry_plugin_applicable = request.REQUEST.get('entry_plugin_applicable')
    if (entry_plugin_applicable == 'y'):
        entry_plugin_applicable = True
    else:
        entry_plugin_applicable = False
    isQuizOrTestApplicable = request.REQUEST.get('isQuizOrTestApplicable');
    if (isQuizOrTestApplicable == 'y'):
        isQuizOrTestApplicable = True
    else:
        isQuizOrTestApplicable = False

    if (helper.isSocial()):
        tags = tags.pop(0)
        temp = tags.split(',')
        tags = []
        for t in temp:
            x = urllib.unquote(t)
            tags.append(x)
    set = helper.getCache(setKey)
    entry = SetEntry(set = set, question = question, answer = answer, tags = tags, isPluginApplicable = entry_plugin_applicable, isQuizOrTestApplicable = isQuizOrTestApplicable)
    entry.put()
    set.entryCount = set.entryCount + 1
    set.deleteCache(all = True)
    set.put()
    key_as_str = str(entry.key())
    return_data = {'status': 'ok', 'entryKey': key_as_str, 'question': question, 'answer': answer, 'tags': tags, 'entry_plugin_applicable': entry_plugin_applicable}
    return return_data

def set_subscribe_json(request,user,context,helper):
    setKey = request.REQUEST.get('setKey')      
    set = helper.getCache(setKey)
    set.subscribe(user)
    context['title'] = set.title                    
    return setlist_json(request, user, context, helper)

def deck_subscribe_withoutsetlist_json(request,user,context,helper):
    setKey = request.REQUEST.get('setKey')      
    set = helper.getCache(setKey)
    set.subscribe(user)
    return_data = {}
    return_data['status'] = 'Success'
    return return_data

def deck_unsubscribe_withoutsetlist_json(request,user,context,helper):
    setKey = request.REQUEST.get('setKey')      
    set = helper.getCache(setKey)
    set.unsubscribe(user)
    return_data = {}
    return_data['status'] = 'Success'
    return return_data

def library_delete_not_authored_decks_json(request,user,context,helper):
    setKeys = request.REQUEST.getlist('setKeys[]')
    for setKey in setKeys:
        set = helper.getCache(setKey)
        set.library_delete_not_authored_deck(user)

    return_data = {}
    return_data['status'] = 'Success'
    return return_data

def delete_cards_json(request,user,context,helper):
    entryKeys = request.REQUEST.getlist('entryKeys[]')
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    for entryKey in entryKeys:
        entry = helper.getCache(entryKey)
        set.entryCount = set.entryCount - 1
        helper.deleteCache(entryKey)
        entry.delete()

    set.put()
    set.deleteCache(all = True)
    return_data = {}
    return_data['status'] = 'Success'
    return return_data

def library_delete_authored_decks_json(request,user,context,helper):
    setKeys = request.REQUEST.getlist('setKeys[]')
    for setKey in setKeys:
        set = helper.getCache(setKey)
        set.library_delete_authored_deck(user)

    return_data = {}
    return_data['status'] = 'Success'
    return return_data

def setlist_json(request,user,context,helper):
    return_data = {}
    if (user):
        preferences = Preferences.gql("WHERE user = :user", user = user).get()
        if (not preferences):
            preferences = Preferences(user = user)
            preferences.put()
        if ('setKey' in request.REQUEST):
            # This set is added to the setlist without subscribing the user to the set (we allow users to preview a set without subscribing to the set)
            # While we can just append the set being preview to the setlist on the browser side, we want to do it on the server side so that we don't
            # have to sort the setlist on the browser side, and we want to make the preview set persistent across browser refresh, until the user 
            # switch to study another set.  We add the current set being study to the setlist (see below).
            set = helper.getCache(request.REQUEST.get('setKey'))
            preferences = set.makeThisSetCurrentForUser(user)
            # Now we revert what we said earlier.  When a user preview a set, we automatically subscribe him to the set.
            set.subscribe(user)
        else:
            preferences = Preferences.gql("WHERE user = :user", user = user).get()

        currentSubscription = SetCurrentSubscription.gql("WHERE user = :user", user = user).fetch(1000)
        try:
            cs = preferences.current_set
        except:
            preferences.current_set = None
    
        if (preferences.current_set is None) and (len(currentSubscription) > 0):
            preferences = currentSubscription[0].set.makeThisSetCurrentForUser(user)

        current_set_key = ''
        if (preferences.current_set is not None):
            current_set_key = str(preferences.current_set.key())

        # Update the current subscription table with the user's display name and email address
        # Check to see if the current set is in the subscribed list
        # Because we are going to be adding the current set to the list (without adding it to current subscription table)
        # we need to convert rows in current subscription table to an array of sets
        included = False
        userDisplayName = user.getName()
        userEmail = user.getEmail()
        setlist = []
        objective_map = {}
        for cs in currentSubscription:
            k = str(cs.set.key())
            objective_map[k] = cs.objective_calendarDays;
            if ((cs.userDisplayName != userDisplayName) or (cs.userEmail != userEmail)):
                cs.userDisplayName = userDisplayName
                cs.userEmail = userEmail
                cs.put()
            if (str(cs.set.key()) == current_set_key):
                included = True
            setlist.append(cs.set)

        if (not included) and (preferences.current_set is not None):
            setlist.append(preferences.current_set)

        setlist_info = []
        for set in setlist:
            str_key = str(set.key())
            objective_calendarDays = objective_map[str_key]
            title = set.title
            current = 0
            if (str_key == current_set_key):
                current = 1
            setlist_info.append([str_key,title,current, objective_calendarDays])

        setlist_info.sort(sort_setlist)
        return_data['list'] = setlist_info

        # Now on to including the tags list, and entryCount so that browser does not have to do another request
        if (preferences.current_set is not None):
            current_set_key = str(preferences.current_set.key())
            setKey = current_set_key
            set = helper.getCache(setKey)
            context['title'] = set.title
            context['setKey'] = setKey
            currentSubscription = SetCurrentSubscription.gql("WHERE user = :user and set = :set", user = user, set = set).get();
            context['subscribed'] = False
            context['contributor'] = set.isContributor(user)
            if currentSubscription:
                context['subscribed'] = True                              
            tags = set.tags()
            return_data['tags'] = tags
            return_data['isContributor'] = context['contributor']
            return_data['entryCount'] = set.entryCount
            if (set.shortUrl is None):
                bitly = BitLy(settings.BITLY_USERNAME, settings.BITLY_API_KEY);
                setUrl = context['baseURL'] + "/set/study.html?setKey=" + setKey
                set.shortUrl = bitly.shorten(setUrl)
                set.put()
            return_data['shortUrl'] = set.shortUrl
            return_data['title'] = set.title
            return_data['setKey'] = setKey
    return return_data

def set_info_json(request, user, context, helper):
    return_data = {}
    set = helper.getCache(request.REQUEST.get('setKey'))
    return_data['title'] = set.title;
    option_list = '';
    for v in ['public', 'private']:
        option_list = option_list + '<option value="' + v + '"'
        if (v == set.visibility):
            option_list = option_list + ' selected'
        option_list = option_list + '>' + v + '</option>'
    return_data['option_list'] = option_list;
    return_data['materials'] = set.studyMaterials
    return_data['description'] = set.description
    if (user.getEmail() is None):
        return_data['askForEmail'] = True
    return return_data

def entry_get_json(request, user, context, helper):
    entryKey = request.REQUEST.get('entryKey')
    entry = helper.getCache(entryKey)
    return_data = {}
    tags_json = {}
    if (not entry):
        return_data['tags'] = [];
        return_data['tags_json'] = {}
        return_data['q'] = 'This entry has been deleted!'
        return_data['a'] = 'This entry has been deleted!'
        return_data['deleted'] = True
        return_data['entryKey'] = entryKey
        return return_data

    for i in entry.tags:
        tags_json[i] = True
    return_data['tags'] = entry.tags;
    return_data['tags_json'] = tags_json
    return_data['q'] = entry.question
    return_data['a'] = entry.answer
    return_data['entryKey'] = entryKey
    return_data['isPluginApplicable'] = entry.isPluginApplicable
    return_data['isQuizOrTestApplicable'] = entry.isQuizOrTestApplicable

    setKey = request.REQUEST.get('setKey')
    view = request.REQUEST.get('view')
    if (view == "study"):
        result = StudyResult.gql("WHERE user = :user AND entry = :entry", user = user, entry = entry).get();
        if (not result):
            set = helper.getCache(setKey)
            entry = helper.getCache(entryKey)
            result = StudyResult(user = user, set = set, entry = entry)
            result.shown = int(request.REQUEST.get('shown'))
            result.wrong = int(request.REQUEST.get('wrong'))
            result.ratio = float(result.wrong / result.shown)
            result.put();
    return return_data

def entry_update_json(request, user, context, helper):
    entryKey = request.REQUEST.get('entryKey')
    setKey = request.REQUEST.get('setKey')
    question = request.REQUEST.get('question')
    answer = request.REQUEST.get('answer')
    tags = request.REQUEST.getlist('tags[]')
    entry_plugin_applicable = request.REQUEST.get('entry_plugin_applicable')
    if (entry_plugin_applicable == 'y'):
        entry_plugin_applicable = True
    else:
        entry_plugin_applicable = False
    isQuizOrTestApplicable = request.REQUEST.get('isQuizOrTestApplicable');
    if (isQuizOrTestApplicable == 'y'):
        isQuizOrTestApplicable = True
    else:
        isQuizOrTestApplicable = False
    return_data = { }
    if (question or answer):
        set = helper.getCache(setKey)
        existed = []
        tags_dict = set.tags_dict()
        for t in tags:
            if ((t) and (t in tags_dict)):
                existed.append(t)
        entry = helper.getCache(entryKey)
        entry.question = question
        entry.answer = answer
        entry.tags = existed
        entry.isPluginApplicable = entry_plugin_applicable
        entry.isQuizOrTestApplicable = isQuizOrTestApplicable
        entry.put()              
        set.deleteCache(all = True)
        helper.deleteCache(str(entry.key()))
    return return_data

def entry_delete_json(request, user, context, helper):
    entryKey = request.REQUEST.get('entryKey')      
    entry = helper.getCache(entryKey)
    set = entry.set;
    set.entryCount = set.entryCount - 1
    set.put()
    set.deleteCache(all = True)
    helper.deleteCache(entryKey)
    entry.delete()
    return {}

def set_reorder_entries_json(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    entryKey1 = request.REQUEST.get('entryKey1')
    entryKey2 = request.REQUEST.get('entryKey2')
    set = helper.getCache(setKey)
    entry1 = helper.getCache(entryKey1)
    entry2 = helper.getCache(entryKey2)
    entry1.whenAdded = entry2.whenAdded + datetime.timedelta(seconds=1)
    entry1.put();
    helper.deleteCache(str(entry1.key()))
    return {}

def set_study_json(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    tag = request.REQUEST.get('tag');
    set = helper.getCache(setKey)
    seen = {}
    contributor = set.isContributor(user);
    ordered_list = []
    if (tag == '+'):
        entries = memcache.get('_entries' + str(set.key()));
        if entries is not None:
            entries = pickle.loads(entries)
        else:
            entries = SetEntry.gql("WHERE set = :set", set = set)
            memcache.set('_entries' + str(set.key()), pickle.dumps(entries))
    elif (tag == '-'):
        entries = memcache.get('_entries_notag' + str(set.key()));
        if entries is not None:
            entries = pickle.loads(entries)
        else:
            entries = []
            queryResults = SetEntry.gql("WHERE set = :set", set = set)
            for entry in queryResults:
                if (len(entry.tags) == 0):
                    entries.append(entry)
        memcache.set('_entries_notag' + str(set.key()), pickle.dumps(entries))
    else:
        entries = memcache.get(tag + str(set.key()));
        if entries is not None:
            entries = pickle.loads(entries)
        else:
            entries = SetEntry.gql("WHERE set = :set AND tags = :tag", set = set, tag = tag)
            memcache.set(tag + str(set.key()), pickle.dumps(entries))
    for entry in entries:
        key_as_str = str(entry.key())
        if (key_as_str not in seen):
            data = {}
            data['shown'] = 0
            data['wrong'] = 0
            data['entryKey'] = key_as_str
            data['setKey'] = setKey
            ordered_list.append(data)
    return {'entries': ordered_list, 'contributor': contributor}

def set_render_json(request, user, context, helper):
    return_data = {}
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    context['title'] = set.title
    context['setKey'] = setKey
    currentSubscription = SetCurrentSubscription.gql("WHERE user = :user and set = :set", user = user, set = set).get();
    context['subscribed'] = False
    context['contributor'] = set.isContributor(user)
    if currentSubscription:
        context['subscribed'] = True                              
    set.makeThisSetCurrentForUser(user)
    template = get_template('astoundlearning_app/ajax/renderSet.html')
    return_data['renderSetHTML'] = template.render(Context(context))
    tags = set.tags()
    return_data['tags'] = tags;
    return return_data

def set_send_feedback_json(request, user, context, helper):
    feedback = request.REQUEST.get('feedback')
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    sender = user.getEmail()
    if not sender:
        sender = request.REQUEST.get('email')
    if sender:
        user.email = sender;
        helper.deleteCache(str(user.key()))
        user.put();
        subject = context['siteName'] + ' - Feedback on ' + set.title
        to = set.getContributorEmails()
        return_data = {}
        return_data['status'] = 'ok'
        if (sender) and (to):
            # djangomail.send_mail(subject, body, sender,[to])
            mail.send_mail(sender = sender, to = to, subject = subject, body = feedback, html = feedback)
        else:
            return_data['status'] = 'failed'
        return return_data;

def save_objective_json(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    objective_calendarDays = request.REQUEST.get('objective_calendar_days')
    set = helper.getCache(setKey)
    cs = SetCurrentSubscription.gql("WHERE user = :user AND set = :set", user = user, set = set).get()
    cs.objective_calendarDays = int(objective_calendarDays)
    cs.objective_set_on = datetime.date.today()
    cs.put()

def set_save_json(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    set.title = request.REQUEST.get('title')
    set.visibility = request.REQUEST.get('visibility')
    set.studyMaterials = request.REQUEST.get('materials')
    set.description = request.REQUEST.get('description')
    set.deleteCache(all = False)
    set.put()
    email = request.REQUEST.get('email')
    if (email):
        user.email = email;
        helper.deleteCache(str(user.key()))
        user.put();
    return setlist_json(request, user, context, helper)

def deck_edit_save_html(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    set.title = request.REQUEST.get('title')
    set.visibility = request.REQUEST.get('visibility')
    set.studyMaterials = request.REQUEST.get('materials')
    set.description = request.REQUEST.get('description')
    set.deleteCache(all = False)
    set.put()
    email = request.REQUEST.get('email')
    if (email):
        user.email = email;
        helper.deleteCache(str(user.key()))
        user.put();

def display_preferences_page(request, user, context, helper):
    preferences = Preferences.gql("WHERE user = :user", user = user).get()
    context['name'] = user.getName()
    context['email'] = user.getEmail()
    context['displayTagColumn_yes'] = ''
    context['displayTagColumn_no'] = ''
    context['orientation_question_above_answer'] = ''
    context['orientation_side_by_side'] = ''
    context['allow_advertising_yes'] = ''
    context['allow_advertising_no'] = ''
    context['gender_male'] = ''
    context['gender_female'] = ''
    checked = 'checked="checked"'
    if (preferences.displayTagColumn == 'Y'):
        context['displayTagColumn_yes'] = checked
    else:
        context['displayTagColumn_no'] = checked
    if (preferences.orientation == 'question-above-answer'):
        context['orientation_question_above_answer'] = checked
    else:
        context['orientation_side_by_side'] = checked
    if (preferences.allowAdvertising == 'Y'):
        context['allow_advertising_yes'] = checked
    else:
        context['allow_advertising_no'] = checked
    if (preferences.gender == 'Male'):
        context['gender_male'] = checked
    else:
        context['gender_female'] = checked

    d = datetime.datetime.now()
    thisyear = d.year
    yearMaximumAge = thisyear - 122
    yearMinimumAge = thisyear - 4
    birthYearOptions = ""
    for i in range(yearMaximumAge,yearMinimumAge):
        if (str(i) == preferences.birthYear):
            birthYearOptions = birthYearOptions + '<option value="' + str(i) + '" selected="selected">' + str(i) + '</option>';
        else:
            birthYearOptions = birthYearOptions + '<option value="' + str(i) + '">' + str(i) + '</option>';
    context['birthYearOptions'] = birthYearOptions
    locations = Location.gql("WHERE user = :user ORDER BY location_name", user = user).fetch(10)
    location_dropdown = ''
    found = 0
    for loc in locations:
        found = found + 1
        location_dropdown += '<option value="' + str(loc.key()) + '">' + loc.location_name + '</option>'
    context['location_dropdown'] = location_dropdown
    if (found == 0):
        context['location_display'] = 'display:none'
    else:
        context['location_display'] = ''

def add_location_json(request, user, context, helper):
    # Create the location if it is not already exist
    locationName = request.REQUEST.get('locationName')
    location = Location.gql("WHERE user = :user and location_name = :location_name", user = user, location_name = locationName).get()
    if (not location):
        location = Location(user = user, location_name = locationName)
        location.put()
    # Associate the currently subscribe decks to this location
    results = SetCurrentSubscription.gql("WHERE user = :user", user = user).fetch(1000)
    location_decks = []
    for r in results:
        location_decks.append(LocationDeck(location = location, deck = r.set))
    db.put(location_decks)

    # Returns the list of locations.  Front-end code will re-load
    # the dropdown list of locations, and load the list of decks
    # that were just associated with this new location via the
    # onchange handler
    locations = []
    new_location_key = str(location.key())
    results = Location.gql("WHERE user = :user ORDER BY location_name", user = user).fetch(10)
    for r in results:
        k = str(r.key())
        is_new = ""
        if (k == new_location_key):
            is_new = 'selected="selected"'
        locations.append([k, r.location_name, is_new])
    return_data = {}
    locations.sort(sort_locations)
    return_data['locations'] = locations
    return return_data

def get_location_list_json(request, user, context, helper):
    current_location_key = request.REQUEST.get('current_location_key');
    locations = []
    results = Location.gql("WHERE user = :user ORDER BY location_name", user = user).fetch(10)
    for r in results:
        k = str(r.key())
        is_selected = 0
        if (k == current_location_key):
            is_selected = 1
        locations.append([k, r.location_name, is_selected])
    return locations

def plugin_get_decks(request, user, context, helper):
    # If the user did not specify the location for the plugin, retrieve all decks from current subscriptions.
    # If given a location key that no longer exist, retrieve all decks from current subscriptions.
    decks = []
    all_decks = []
    current_location_key = request.REQUEST.get('current_location_key');
    if (current_location_key):
        location = helper.getCache(current_location_key);
    else:
        location = None

    # first get other information from current subscription
    cs_info = {}
    cs = SetCurrentSubscription.all().filter('user = ', user).fetch(500);
    for r in cs:
        set_key = str(r.set.key())
        cs_info[set_key] = [r.objective_set_on, r.objective_calendarDays]
        all_decks.append(r.set)
    # determine the list of decks
    if (location is None):
        decks = all_decks
    else:
        ld = LocationDeck.all().filter('location = ', location).fetch(500)
        for r in ld:
            decks.append(r.deck)
    results = []
    for r in decks:
        set_key = str(r.key())
        objective_set_on = cs_info[set_key][0]
        if (objective_set_on):
            results.append([str(r.key()),r.title])
    return results

def plugin_get_entries_by_day(all_cards, day_number, cards_per_day):
    if (day_number < 0):
        return []
    if (len(all_cards) == 0):
        return []
    start_index = day_number * cards_per_day
    end_index = start_index + cards_per_day
    return all_cards[start_index : end_index]

# This function is not invoked directly via the browser,
# therefore the third argument is not the normal context object
# This function retrieve a list of entries for the given deck
def plugin_get_entries(request, user, deck_key, helper):
    deck = helper.getCache(deck_key)
    cs = SetCurrentSubscription.gql("WHERE user = :user AND set = :set", user = user, set = deck).get();
    if (cs.objective_set_on is None):
        return []
    # determine the number of cards that the user should study per day based on his objective
    entryCount = deck.entryCount
    cardsPerDay = math.ceil(float(entryCount) / cs.objective_calendarDays)
    cardsPerDay = int(cardsPerDay)
    # For now we decide to go with hard coded values:
    # 20% of the user time is spent on new cards
    # 10% is spent on material covered yesterday
    # 10% is spent on meterial covered 2 day ago
    # 10% is spent on material covered 3 day ago
    # ...
    # 10% is spent on material covered over 7 days ago
    # For now we decide to get the list of entries by querying the deck order by time when
    # the card was put into the system.  We can also use data in StudyResult table.  Either
    # way, we will need to pull data from both table

    # reset the objective_set_on date if today exceed objective_calendarDays
    if (datetime.date.today() - cs.objective_set_on > datetime.timedelta(days = cs.objective_calendarDays)):
        cs.objective_set_on = datetime.date.today()
        db.put(cs)
    
    # This plugin assume that the user study everyday, and is able to cover all materials for that day
    # determine what day is today
    time_delta = datetime.date.today() - cs.objective_set_on
    day_diff = time_delta.days

    # first get all the cards in this deck
    all_cards = []
    cards = SetEntry.all().filter('set =', deck).filter('isPluginApplicable =', True).fetch(100)
    while cards:
        x = cards[-1].key()
        for r in cards:
            all_cards.append(r)
        cards = SetEntry.all().filter('set =', deck).filter('isPluginApplicable =', True).filter('__key__ >', x).fetch(100)
    # sort the cards based on whenAdded
    all_cards.sort(plugin_sort_entries_whenAdded)

    # To make sure that we consider both whenAdded and information from StudyResult, we first
    # put the cards into buckets
    buckets = []
    buckets.append([])
    buckets.append([])
    buckets.append([])
    buckets.append([])
    buckets.append([])
    buckets.append([])
    buckets.append([])
    buckets.append([])
    buckets.append([])
    buckets[0] = plugin_get_entries_by_day(all_cards, day_diff, cardsPerDay)
    buckets[1] = plugin_get_entries_by_day(all_cards, day_diff - 1, cardsPerDay)
    buckets[2] = plugin_get_entries_by_day(all_cards, day_diff - 2, cardsPerDay)
    buckets[3] = plugin_get_entries_by_day(all_cards, day_diff - 3, cardsPerDay)
    buckets[4] = plugin_get_entries_by_day(all_cards, day_diff - 4, cardsPerDay)
    buckets[5] = plugin_get_entries_by_day(all_cards, day_diff - 5, cardsPerDay)
    buckets[6] = plugin_get_entries_by_day(all_cards, day_diff - 6, cardsPerDay)
    buckets[7] = plugin_get_entries_by_day(all_cards, day_diff - 7, cardsPerDay)
    if (day_diff > 7):
        end_index = (day_diff - 7) * cardsPerDay
        buckets[8] = all_cards[0:end_index]
    # Get all information from StudyResult
    study_history = {}
    study_results = StudyResult.all().filter('set =', deck).filter('user =', user).fetch(100)
    while study_results:
        x = study_results[-1].key()
        for r in study_results:
            try:
                card_key = str(r.entry.key())
            except:
                db.delete(r)
                continue
            info = {}
            info['lastDisplayedTime'] = r.lastDisplayedTime
            info['firstDisplayedTime'] = r.firstDisplayedTime
            info['shown'] = r.shown
            info['wrong'] = r.wrong
            info['ratio'] = r.ratio
            study_history[card_key] = info
        study_results = StudyResult.all().filter('set =', deck).filter('user =', user).filter('__key__ >', x).fetch(100)
    # Becauses buckets[i] is a list of cards, and do not know if we can add extra information
    # to the cards, so we have to copy informations to buckets2[i]
    buckets2 = []
    buckets2.append([])
    buckets2.append([])
    buckets2.append([])
    buckets2.append([])
    buckets2.append([])
    buckets2.append([])
    buckets2.append([])
    buckets2.append([])
    buckets2.append([])
    for i in range(len(buckets)):
        bucket = buckets[i]
        for j in range(len(bucket)):
            card = bucket[j]
            card_info = {}
            card_info['question'] = card.question
            card_info['answer'] = card.answer
            card_info['tags'] = card.tags
            card_key = str(card.key())
            card_info['key'] = card_key
            card_info['deck_key'] = deck_key
            if (card_key in study_history):
                study_history_info = study_history[card_key]
                card_info['shown'] = study_history_info['shown']
                card_info['wrong'] = study_history_info['wrong']
                card_info['ratio'] = study_history_info['ratio']
            else:
                card_info['shown'] = 0
                card_info['wrong'] = 0
                card_info['ratio'] = 0
            buckets2[i].append(card_info)
    
    # now sort cards (using information from StudyResult), and exclude some cards
    # that the user is already familiar with
    for i in range(len(buckets2)):
        bucket = buckets2[i]
        bucket.sort(plugin_sort_entries_history)
        if (i > 1):
            end_index = int(math.ceil(len(bucket) / 2))
            bucket = bucket[0:end_index]
        buckets2[i] = bucket
    # now consolidate cards from each buckets into a single array
    cards = []
    for i in range(len(buckets2)):
        bucket = buckets2[i]
        for j in range(len(bucket)):
            cards.append(bucket[j])
    # return the result
    return cards

# Note: the functions that are prefix by plugin_ are used by the plugin.
# These functions has the same signature as other functions.  However,
# the second parameter (user) is not a valid user object.  Therefore,
# this function must return the user object if it succeed.
# This function is not used directly, even though it has the same signature as other functions
def plugin_login(request, user, context, helper):
    username = request.REQUEST.get('username');
    md5password = request.REQUEST.get('md5password');
    user = CustomUser.gql("WHERE email = :email", email = username).get();
    if (user):
        p = Preferences.gql("WHERE user = :user", user = user).get();
        if (p.pluginPassword == md5password):
            return user
    return False

def plugin_mark_not_applicable_json(request, user, context, helper):
    return_data = {}
    return_data['login_status'] = 'failure'
    user = plugin_login(request, user, context, helper)
    if (user):
        return_data['login_status'] = 'success'
        entryKey = request.REQUEST.get('entryKey') 
        card = helper.getCache(entryKey)
        card.isPluginApplicable = False
        card.put()
    return return_data

def plugin_record_entry_displayed_json(request, user, context, helper):
    return_data = {}
    return_data['login_status'] = 'failure'
    user = plugin_login(request, user, context, helper)
    if (user):
        return_data['login_status'] = 'success'
        entry_get_json(request, user, context, helper)
    return return_data

def plugin_peek_json(request, user, context, helper):
    return_data = {}
    return_data['login_status'] = 'failure'
    user = plugin_login(request, user, context, helper)
    if (user):
        return_data['login_status'] = 'success'
        entry_process_show_answer_json(request, user, context, helper)
    return return_data

def plugin_get_cards_for_deck_json(request, user, context, helper):
    return_data = {}
    return_data['login_status'] = 'failure'
    user  = plugin_login(request, user, context, helper)
    if (user):
        return_data['login_status'] = 'success'
        deck_key = request.REQUEST.get('deck_key')
        return_data['cards'] = plugin_get_entries(request, user, deck_key, helper)
        return_data['deck_key'] = deck_key
    return return_data

def plugin_login_json(request, user, context, helper):
    return_data = {}
    return_data['login_status'] = 'failure'
    user = plugin_login(request, user, context, helper)
    if (user):
        return_data['login_status'] = 'success'
        return_data['decks'] = plugin_get_decks(request, user, context, helper)
        return_data['cards'] = []
        if (len(return_data['decks'])):
            key = return_data['decks'][0][0]
            return_data['cards'] = plugin_get_entries(request, user, key, helper)
    return return_data

def plugin_locations_json(request, user, context, helper):
    return_data = {}
    return_data['login_status'] = 'failure'
    user = plugin_login(request, user, context, helper)
    if (user):
        return_data['login_status'] = 'success'
        return_data['locations'] = get_location_list_json(request, user, context, helper)
    return return_data

def delete_location_json(request, user, context, helper):
    locationKey = request.REQUEST.get('locationKey')
    location = helper.getCache(locationKey)
    results = LocationDeck.gql("WHERE location = :location", location = location).fetch(500)
    db.delete(results)
    location.delete()
    helper.deleteCache(locationKey)
    locations = []
    results = Location.gql("WHERE user = :user", user = user).fetch(10)
    for r in results:
        k = str(r.key())
        locations.append([k, r.location_name, ''])
    return_data = {}
    locations.sort(sort_locations)
    return_data['locations'] = locations
    return return_data

def save_location_decks_json(request, user, context, helper):
    # This function associate selected decks with this location
    # But first, to prevent adding a deck to the same location multiple
    # times, we first delete all existing linkages / associations
    locationKey = request.REQUEST.get('locationKey')
    location = helper.getCache(locationKey)
    selectedDecks = request.REQUEST.getlist('selectedDecks[]');
    selected = {}
    for k in selectedDecks:
        selected[k] = True
    # Delete all existing linkages / associations
    results = LocationDeck.gql("WHERE location = :location", location = location).fetch(500);
    db.delete(results)
    # Associate selected decks with this location
    results = SetCurrentSubscription.gql("WHERE user = :user", user = user).fetch(1000)
    addThese = []
    for r in results:
        k = str(r.set.key())
        if (k in selected):
            addThese.append(LocationDeck(location = location, deck = r.set))
    db.put(addThese)
    return_data = {}
    return return_data

def display_location_decks_json(request, user, context, helper):
    locationKey = request.REQUEST.get('locationKey')
    location = helper.getCache(locationKey)
    location_decks = {}
    results = LocationDeck.gql("WHERE location = :location", location = location).fetch(250)
    for r in results:
        d = r.deck;
        k = str(d.key())
        location_decks[k] = True
    decks = []
    results = SetCurrentSubscription.gql("WHERE user = :user", user = user).fetch(1000)
    for r in results:
        d = r.set
        k = str(d.key())
        objective_calendarDays = r.objective_calendarDays
        set_on = ''
        if (r.objective_set_on):
            set_on = r.objective_set_on.isoformat()
        checked = ''
        if (k in location_decks):
            checked='checked="checked"'
        decks.append([k, d.title, checked, objective_calendarDays, set_on, d.entryCount])
    decks.sort(sort_location_decks)
    return_data = {}
    return_data['decks'] = decks
    return return_data

def save_preferences_json(request, user, context, helper):
    p = Preferences.gql("WHERE user = :user", user = user).get()
    p.displayTagColumn = request.REQUEST.get('displayTagColumn')
    p.orientation = request.REQUEST.get('orientation')
    p.allowAdvertising = request.REQUEST.get('allowAdvertising')
    p.gender = request.REQUEST.get('gender')
    p.birthYear = request.REQUEST.get('birthYear')
    newPluginPassword = request.REQUEST.get('pluginPassword');
    if (len(newPluginPassword) >= 4):
        m = hashlib.md5();
        m.update(newPluginPassword)
        p.pluginPassword = m.hexdigest()
    p.put()
    user.name = request.REQUEST.get('displayName')
    user.email = user.googleAccount.email();
    user.put()

def set_remove_contributors_json(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    contributors = []
    selected = request.REQUEST.getlist('contributors[]')
    if (helper._isValidSignature()):
        selected = selected.pop(0);
        selected = selected.split(',')
    for c in selected:
        contributor = helper.getCache(c)
        contributors.append(contributor)
    set.removeContributors(user,contributors)
    return {}

def set_search_for_potential_contributors_json(request, user, context, helper):
    # For now, we don't have a mechanism where contributor can invite (via email) others (who might
    # or might not already be a user of our software / service).  So to give a user the ability to
    # contribute (full privileges), that user must currently subscribe to the set, and an existing
    # contributor can search for potential contributor via email (if the potential contributor is
    # a Gmail user), or via name (if the potential contributor is a social-network user)
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    nameOrEmail = request.REQUEST.get('nameOrEmail')
    results = SetCurrentSubscription.gql("WHERE set = :set AND userDisplayName = :userDisplayName", set = set, userDisplayName = nameOrEmail)
    found = []
    seen = {}
    for r in results:
        t = {};
        if (r.user == user):
            continue
        k = t['key'] = str(r.user.key())
        seen[k] = True
        t['displayName'] = r.user.getName()
        found.append(t)
    results = SetCurrentSubscription.gql("WHERE set = :set AND userEmail = :userEmail", set = set, userEmail = nameOrEmail)
    for r in results:
        t = {};
        if (r.user == user):
            continue
        k = str(r.user.key())
        if (k not in seen):
            t['key'] = k
            t['displayName'] = r.user.getName() or nameOrEmail
            found.append(t)
    return_data = {'users': found}
    return return_data

def set_add_contributors_json(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    contributors = []
    selected = request.REQUEST.getlist('contributors[]')
    if (helper._isValidSignature()):
        selected = selected.pop(0)
        selected = selected.split(',')
    for c in selected:
        contributor = helper.getCache(c)
        contributors.append(contributor)
    set.addContributors(user,contributors)
    return {}

def social_user_update_display_name_json(request, user, context, helper):
    user.name = request.REQUEST.get('displayName')
    user.put()
    return {}

def our_feedback_send_json(request, user, context, helper):
    ourFeedback = request.REQUEST.get('ourFeedback')
    sender = user.getEmail()
    if not sender:
        sender = request.REQUEST.get('email')
    if sender:
        user.email = sender;
        helper.deleteCache(str(user.key()))
        user.put();
    else:
        sender = 'khaitdoan@gmail.com'
    to = 'khaitdoan@gmail.com'
    entry = FeedbackForAstoundLearning(user = user, feedback = ourFeedback)
    entry.put()
    mail.send_mail(sender = sender, to = to, subject = context['applicationName'] + ' Feedback', body = ourFeedback, html = ourFeedback)
    return {}

def search_decks_html(request, user, context, helper):
    return set_search_html(request, user, context, helper)

def ourFeedback_send_html(request, user, context, helper):
    ourFeedback = request.REQUEST.get('feedback')
    sender = user.getEmail()
    if not sender:
        sender = request.REQUEST.get('email')
    if sender:
        user.email = sender;
        helper.deleteCache(str(user.key()))
        user.put();
    else:
        sender = 'khaitdoan@gmail.com'
    to = 'khaitdoan@gmail.com'
    entry = FeedbackForAstoundLearning(user = user, feedback = ourFeedback)
    entry.put()
    mail.send_mail(sender = sender, to = to, subject = context['applicationName'] + ' Feedback', body = ourFeedback, html = ourFeedback)
    return {}

def set_display_contributors_html(request, user, context, helper):
    # list the current contributors so that current contributor can remove themselve if they no longer contributing to the 
    # set.
    # Display an interface so that contributors can search existing user by name
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    contrib = set.getContributors()
    contributors = []
    seen = {}
    for c in contrib:
        t = {}
        k = str(c.key())
        if (k not in seen):
            t['key'] = str(c.key())
            t['name'] = c.getName()
            t['email'] = c.getEmail()
            if (context['host'] == 'kdoan.dyndns.org'):
                t['debug'] = True
            contributors.append(t)
            seen[k] = True
    context['contributors'] = contributors

def set_render_browser_plugin_tab_html(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    plugin_applicable = set.isPluginApplicable(user)
    if (plugin_applicable):
        context['plugin_applicable_y'] = 'checked="checked"'
        context['plugin_applicable_n'] = ''
    else:
        context['plugin_applicable_n'] = 'checked="checked"'
        context['plugin_applicable_y'] = ''

def set_display_study_materials_html(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    context['materials'] = set.studyMaterials or "Author / Contributors of this set did not recommend any study materials."

def set_display_edit_form_html(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    context['title'] = set.title
    context['setKey'] = setKey
    option_list = '';
    for v in ['public', 'private']:
        option_list = option_list + '<option value="' + v + '"'
        if (v == set.visibility):
            option_list = option_list + ' selected'
        option_list = option_list + '>' + v + '</option>'
    context['option_list'] = option_list
    context['materials'] = set.studyMaterials
    context['description'] = set.description
    if (user.getEmail() is None):
        context['askForEmail'] = True

def set_share_html(request, user, context, helper):
    mode = request.REQUEST.get('m')
    if (not mode):
        # User clicks on the "Share" tab
        setKey = request.REQUEST.get('setKey')
        set = helper.getCache(setKey)
        context['contributor'] = set.isContributor(user)
        if ((user.getEmail() is None) or (user.getEmail() == '')):
            context['noFeedbackWarning'] = True
        else:
            if (helper._isValidSignature()):
                setKey = request.REQUEST.get('setKey')
                set = helper.getCache(setKey)
                set.subscribe(user)
                set.makeThisSetCurrentForUser(user)
            elif (not user):
                context['loginURL'] = users.create_login_url(request.build_absolute_uri())
            else:
                setKey = request.REQUEST.get('setKey')
                set = helper.getCache(setKey)
                set.subscribe(user)
                set.makeThisSetCurrentForUser(user)

def deck_display_feedback_form_html(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    context['deck_title'] = set.title
    if (set.getContributorEmails() is None):
        context['noContributorEmails'] = True
    context['contributor'] = set.isContributor(user)
    context['promptForEmail'] = False
    sender = user.getEmail()
    if not sender:
        context['promptForEmail'] = True

def set_study_non_ajax_html(request, user, context, helper):
    if (user):
        if (user.isSocial):
            setKey = request.REQUEST.get('setKey')
            set = helper.getCache(setKey)
            set.subscribe(user)
            set.makeThisSetCurrentForUser(user)
        else:
            setKey = request.REQUEST.get('setKey')
            set = helper.getCache(setKey)
            set.subscribe(user)
            set.makeThisSetCurrentForUser(user)
    else:
        context['loginURL'] = users.create_login_url(request.build_absolute_uri())

def set_study_html(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    set = helper.getCache(setKey)
    context['title'] = set.title
    context['setKey'] = setKey
    tags = set.tags()
    taglist = '<option value="+">' + 'Please select a tag.' + '</option>';
    taglist = taglist + '<option value="-">' + 'Entries with no tags' + '</option>';
    for t in tags:
        if (t == '-'):
            continue
        taglist = taglist + '<option value="' + t + '">' + t + '</option>'
    context['taglist'] = taglist

def set_study_view_search_html(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    entryKey = request.REQUEST.get('entryKey')
    searchTerm = request.REQUEST.get('searchTerm').lower()
    set = helper.getCache(setKey)
    entry = helper.getCache(entryKey)
    resultSet = SetEntry.gql("WHERE set = :set", set = set.key())       
    entries = []
    for r in resultSet:
        q = r.question.lower()
        a = r.answer.lower()
        if ((q.find(searchTerm) > -1) or (a.find(searchTerm) > -1)):
            entries.append(r)
    context['entries'] = entries;                                   
    context['setKey'] = setKey;
    context['contributor'] = set.isContributor(user)
    context['entryCount'] = set.entryCount;

def set_unsubscribe_html(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')      
    confirmed = request.REQUEST.get('confirmed')
    context['confirmed'] = confirmed
    context['setKey'] = setKey
    set = helper.getCache(setKey)
    context['title'] = set.title
    if (confirmed == '1'):
        set.unsubscribe(user)

def set_subscribe_html(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')      
    set = helper.getCache(setKey)
    set.subscribe(user)
    context['title'] = set.title                    

def set_list_view_search_html(request, user, context, helper):
    setKey = request.REQUEST.get('setKey')
    searchTerm = request.REQUEST.get('searchTerm').lower()
    tag = request.REQUEST.get('tag')
    set = helper.getCache(setKey)
    entries = []
    if (searchTerm != ''):
        seen = {}
        if (set.entryCount <= 1000):
            resultSet = SetEntry.gql("WHERE set = :set ORDER BY whenAdded ASC", set = set)
            for r in resultSet:
                key = str(r.key())
                if key not in seen:
                    seen[key] = True
                    q = r.question.lower()
                    a = r.answer.lower()
                    if ((q.find(searchTerm) > -1) or (a.find(searchTerm) > -1)):
                        entries.append(r)
        else:
            tags = set.tags()
            for t in tags:
                resultSet = SetEntry.gql("WHERE set = :set AND tags = :tag ORDER BY whenAdded ASC", set = set, tag = t)
                for r in resultSet:
                    key = str(r.key())
                    if key not in seen:
                        seen[key] = True
                        q = r.question.lower()
                        a = r.answer.lower()
                        if ((q.find(searchTerm) > -1) or (a.find(searchTerm) > -1)):
                            entries.append(r)
    elif (tag == '+'):
        resultSet = SetEntry.gql("WHERE set = :set ORDER BY whenAdded ASC", set = set)
        for r in resultSet:
            entries.append(r)
    elif (tag == '-'):
        # I don't know how to fetch entries that had not been tagged, so we:
        # first, fetch all entries, then filter out entries that had been tagged
        resultSet = SetEntry.gql("WHERE set = :set ORDER BY whenAdded ASC", set = set)
        for r in resultSet:
            if (len(r.tags) == 0):
                entries.append(r)
    elif (tag == '^'):
        # searching for entries that has been marked as not appropriate for the plugin
        resultSet = SetEntry.gql("WHERE set = :set AND isPluginApplicable = :isPluginApplicable ORDER BY whenAdded ASC", set = set, isPluginApplicable = False)
        for r in resultSet:
            entries.append(r)
    else:
        resultSet = SetEntry.gql("WHERE set = :set AND tags = :tag ORDER BY whenAdded ASC", set = set, tag = tag)
        for r in resultSet:
            entries.append(r)
    context['entries'] = entries                                    
    context['setKey'] = setKey
    context['entryCount'] = set.entryCount
    context['contributor'] = set.isContributor(user)

def set_print_html(request, user, context, helper):
    entryKeys = request.REQUEST.getlist('entryKey')
    entries = []
    for entryKey in entryKeys:
        entries.append(helper.getCache(entryKey))
    context['entries'] = entries

def set_new_form_html(request, user, context, helper):
    currentSubscription = SetCurrentSubscription.gql("WHERE user = :user", user = user)
    cs_sets = []
    for cs in currentSubscription:
        cs_sets.append(cs.set)
    context['sets'] = cs_sets
    if (user.getEmail() is None):
        context['askForEmail'] = True

def deck_create_form_html(request, user, context, helper):
    return set_new_form_html(request, user, context, helper)

def deck_display_edit_form(request, user, context, helper):
    set = helper.getCache(request.REQUEST.get('setKey'))
    context['setKey'] = request.REQUEST.get('setKey')
    context['title'] = set.title;
    option_list = '';
    for v in ['public', 'private']:
        option_list = option_list + '<option value="' + v + '"'
        if (v == set.visibility):
            option_list = option_list + ' selected'
        option_list = option_list + '>' + v + '</option>'
    context['option_list'] = option_list;
    context['materials'] = set.studyMaterials
    context['description'] = set.description
    if (user.getEmail() is None):
        context['askForEmail'] = True

def set_search_html(request, user, context, helper):
    authored = {}
    currently_subscribed = {}
    searchTerm = request.REQUEST.get('search')
    sets = StudySet.gql("WHERE author = :author ORDER BY title", author = user)
    for s in sets:
        authored[str(s.key())] = True

    sets = SetCurrentSubscription.gql("WHERE user = :user", user = user)
    for s in sets:
        currently_subscribed[str(s.set.key())] = True

    sets = StudySet.gql("WHERE visibility = 'public' ORDER BY author")
    results = []
    for set in sets:
        title = set.title.lower();
        description = set.description.lower()
        searchTerm = searchTerm.lower()
        if (title.find(searchTerm) > -1) or (description.find(searchTerm) > -1):
            data = {}
            data['title'] = set.title
            data['description'] = set.description
            data['author'] = set.author.getName()
            key_as_str = str(set.key())
            if (key_as_str in authored):
                data['authored'] = True
                if (data['author']):
                    data['author'] = data['author'] + ' <span style="font-style: italic">(You)</span>'
                else:
                    data['author'] = '<span style="font-style: italic">(You)</span>'
            if (key_as_str in currently_subscribed):
                data['current'] = True
            if ((set.subscriptionCount is None) or (set.entryCount is None)):
                set.subscriptionCount = SetCurrentSubscription.gql("WHERE set = :set", set = set).count()
                set.entryCount = SetEntry.gql("WHERE set = :set", set = set).count()
                set.deleteCache(all = False);
                set.put()
            data['subscriptionCount'] = set.subscriptionCount
            data['entryCount'] = set.entryCount
            data['key'] = key_as_str
            results.append(data);
    context['results'] = results;

def display_personal_library_html(request, user, context, helper):
    authored = {}
    currently_subscribed = {}
    sets = StudySet.gql("WHERE author = :author ORDER BY title", author = user)
    for s in sets:
        authored[str(s.key())] = True

    sets = SetCurrentSubscription.gql("WHERE user = :user", user = user)
    for s in sets:
        currently_subscribed[str(s.set.key())] = True

    subscriptions = SetSubscription.gql("WHERE user = :user",user = user)

    subscriptions_t = []
    authored_decks = []
    not_authored_decks = []
    seen = {}
    for s in subscriptions:
        set = s.set
        key_as_str = str(s.set.key())
        if (key_as_str not in seen):
            data = {}
            data['title'] = s.set.title
            data['description'] = s.set.description
            data['author'] = s.set.author.getName()
            if (key_as_str in currently_subscribed):
                data['current'] = True
            if ((set.subscriptionCount is None) or (set.entryCount is None)):
                set.subscriptionCount = SetCurrentSubscription.gql("WHERE set = :set", set = set).count()
                set.entryCount = SetEntry.gql("WHERE set = :set", set = set).count()
                set.deleteCache(all = False);
                set.put()
            data['subscriptionCount'] = set.subscriptionCount
            data['entryCount'] = set.entryCount
            data['key'] = key_as_str
            if (key_as_str in authored):
                if (data['author']):
                    data['author'] = data['author'] + ' <span style="font-style: italic">(You)</span>'
                else:
                    data['author'] = '<span style="font-style: italic">(You)</span>'
                authored_decks.append(data)
            else:
                data['author'] = '<span style="font-style: italic">(Name not specified)</span>'
                not_authored_decks.append(data)
            seen[key_as_str] = True
    authored_decks.sort(sort_decks_personal_library)
    not_authored_decks.sort(sort_decks_personal_library)
    context['authored_decks'] = authored_decks
    context['not_authored_decks'] = not_authored_decks

def display_home_page_html(request, user, context, helper):
    user = context['user']
    if (user):
        preferences = Preferences.gql("WHERE user = :user", user = user).get()
        if (not preferences):
            preferences = Preferences(user = user)
            preferences.put()

def display_authenticated_page(request, user, context, helper):
    #container = helper.get_open_social_container_for_google_friend_connect()
    #viewer = container.fetch_person('@viewer')
    #gfcid = viewer.id
    op = urllib.urlopen('http://www.google.com/friendconnect/api/people/@viewer/@self?fcauth=' + request.COOKIES['fcauth' + settings.GOOGLE_FRIEND_CONNECT_SITE_ID])
    viewer = simplejson.load(op)
    gfcid = viewer['entry']['id']
    context['GOOGLE_FRIEND_CONNECT_USER_ID'] = gfcid

def facebook_canvas_callback_url_html(request,template):
    h = Helper(request)
    h.add_facebook_instance_to_the_request_object()
    redirect = h.require_facebook_login()
    if redirect is not None:
        return redirect
    context = h.prepareContext()
    context['uid'] = request.facebook.uid
    context['css'] = context['combinedCSSForFacebook']

    context['name'] = request.facebook.users.getInfo([request.facebook.uid],['name'])[0]['name']
    response = render_to_response('astoundlearning_app/facebook/canvas/callback/url.html', context)
    h.save_facebook_authentication_data_into_session(response)
    return response
